package com.pll.client;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
import org.springframework.web.socket.client.WebSocketConnectionManager;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import org.springframework.web.util.UriComponentsBuilder;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.net.URI;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * WebSocket客户端组件
 * 负责与WebSocket服务器建立连接、发送消息和接收消息
 * 实现了自动重连机制，确保连接稳定性
 */
@Component
public class WebSocketClient {

    /**
     * WebSocket服务器URL，从配置文件中获取
     */
    @Value("${websocket.server.url}")
    private String websocketServerUrl;

    /**
     * 当前WebSocket会话
     */
    private WebSocketSession session;

    /**
     * WebSocket连接管理器，负责管理连接生命周期
     */
    private WebSocketConnectionManager manager;

    /**
     * 连接状态标志，使用AtomicBoolean确保线程安全
     */
    private final AtomicBoolean isConnected = new AtomicBoolean(false);

    /**
     * WebSocket客户端实现
     */
    private final StandardWebSocketClient client = new StandardWebSocketClient();

    /**
     * 组件初始化方法
     * Bean创建后自动调用，启动WebSocket连接
     */
    @PostConstruct
    public void init() {
        connect();
    }

    /**
     * 连接到WebSocket服务器
     * 配置连接参数并启动连接管理器
     */
    public void connect() {
        try {
            // 构建WebSocket服务器URI
            URI uri = UriComponentsBuilder.fromUriString(websocketServerUrl)
                    .build(true)
                    .toUri();

            // 创建WebSocket处理器，处理连接事件和消息
            manager = new WebSocketConnectionManager(client, new TextWebSocketHandler() {
                /**
                 * 连接建立后的回调方法
                 * 更新连接状态并记录连接信息
                 */
                @Override
                public void afterConnectionEstablished(WebSocketSession newSession) throws Exception {
                    // 保存当前会话到外部类
                    WebSocketClient.this.session = newSession;
                    isConnected.set(true);
                    System.out.println("成功连接到WebSocket服务器: " + newSession.getUri());
                }

                /**
                 * 处理接收到的文本消息
                 * 子类应重写此方法实现具体的消息处理逻辑
                 */
                @Override
                protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
                    System.out.println("收到消息: " + message.getPayload());
                    // 实际应用中应在此处添加消息处理逻辑
                    // 例如：解析消息内容、触发业务逻辑等
                }

                /**
                 * 连接关闭后的回调方法
                 * 更新连接状态并触发重连机制
                 */
                @Override
                public void afterConnectionClosed(WebSocketSession closedSession, CloseStatus status) throws Exception {
                    isConnected.set(false);
                    WebSocketClient.this.session = null; // 重置会话
                    System.out.println("连接关闭，状态码: " + status.getCode() + ", 原因: " + status.getReason());
                    scheduleReconnect(); // 安排重连
                }

                /**
                 * 处理传输错误
                 * 更新连接状态并触发重连机制
                 */
                @Override
                public void handleTransportError(WebSocketSession errorSession, Throwable exception) throws Exception {
                    isConnected.set(false);
                    WebSocketClient.this.session = null; // 重置会话
                    System.err.println("传输错误: " + exception.getMessage());
                    exception.printStackTrace();
                    scheduleReconnect(); // 安排重连
                }
            }, uri);

            // 启动连接管理器
            manager.start();
        } catch (Exception e) {
            System.err.println("连接失败: " + e.getMessage());
            e.printStackTrace();
            scheduleReconnect(); // 连接失败时安排重连
        }
    }

    /**
     * 安排重连任务
     * 使用单独线程延迟执行重连操作
     */
    private void scheduleReconnect() {
        if (!isConnected.get()) {
            new Thread(() -> {
                try {
                    System.out.println("5秒后尝试重连...");
                    TimeUnit.SECONDS.sleep(5); // 延迟5秒
                    if (manager != null) {
                        manager.stop(); // 先停止当前连接
                    }
                    connect(); // 重新连接
                } catch (Exception e) {
                    System.err.println("重连失败: " + e.getMessage());
                    scheduleReconnect(); // 重连失败，递归调用继续尝试
                }
            }).start();
        }
    }

    /**
     * 发送文本消息到WebSocket服务器
     * 检查连接状态并发送消息
     * @param message 要发送的消息内容
     */
    public void sendMessage(String message) {
        if (isConnected.get() && session != null && session.isOpen()) {
            try {
                session.sendMessage(new TextMessage(message));
                System.out.println("消息发送成功: " + message);
            } catch (Exception e) {
                System.err.println("发送失败: " + e.getMessage());
                isConnected.set(false); // 更新连接状态
                scheduleReconnect(); // 安排重连
            }
        } else {
            System.out.println("未连接，无法发送消息");
            scheduleReconnect(); // 安排重连
        }
    }

    /**
     * 组件销毁前的清理方法
     * 关闭WebSocket连接并释放资源
     */
    @PreDestroy
    public void destroy() {
        try {
            if (manager != null) {
                manager.stop();
            }
            if (session != null && session.isOpen()) {
                session.close();
            }
            System.out.println("WebSocket客户端已关闭");
        } catch (Exception e) {
            System.err.println("关闭客户端时出错: " + e.getMessage());
        }
    }

    /**
     * 检查WebSocket连接状态
     * @return 连接状态，true表示已连接，false表示未连接
     */
    public boolean isConnected() {
        return isConnected.get();
    }
}